﻿#include "DAProject.h"
#include "DAWorkFlowGraphicsScene.h"
#include "DAWorkFlowPluginManager.h"
#include "DAGraphicsResizeablePixmapItem.h"
#include "DAStandardNodeLinkGraphicsItem.h"
#include <QBuffer>
#include <QDomDocument>
#include <QFile>
#include <QScopedPointer>
#include <QVariant>
#include <QPen>
#include <QElapsedTimer>
#include "DAAbstractNodeFactory.h"

namespace DA
{
class DAProjectPrivate
{
public:
    DA_IMPL_PUBLIC(DAProject)
    DAProjectPrivate(DAProject* p, DAWorkFlowOperateWidget* widget);

public:
    //保存工作流
    void saveWorkflow(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadWorkflow(DAWorkFlow* workflow, const QDomElement& workflowEle);
    //保存工厂相关的扩展信息
    void saveFactoryInfo(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadFactoryInfo(DAWorkFlow* workflow, const QDomElement& workflowEle);
    //保存工作流的节点
    void saveNodes(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadNodes(DAWorkFlow* workflow, const QDomElement& workflowEle);
    //保存输入输出
    void saveNodeInputOutput(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodeInPutOutputKey(DAAbstractNode::SharedPointer& node, const QDomElement& eleNode);
    //保存属性
    void saveNodePropertys(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodePropertys(DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle);
    //保存item
    void saveNodeItem(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle);
    bool loadNodeItem(DAWorkFlowGraphicsScene* scene, DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle);
    //保存链接
    void saveNodeLinks(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle);
    bool loadNodeLinks(DAWorkFlowGraphicsScene* scene, DAWorkFlow* wf, const QDomElement& workflowEle);
    //保存特殊的item，主要为文本
    void saveSpecialItem(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle);
    bool loadSpecialItem(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle);
    //保存文本
    void saveTextItems(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& textItemsEle);
    bool loadTextItems(DAWorkFlowGraphicsScene* scene, const QDomElement& textItemsEle);
    //保存文本
    void saveTextItem(const DAStandardGraphicsTextItem* item, QDomDocument& doc, QDomElement& textItemEle);
    bool loadTextItem(DAStandardGraphicsTextItem* item, const QDomElement& textItemEle);
    //保存字体
    void saveFont(const QFont& f, QDomDocument& doc, QDomElement& fontEle);
    bool loadFont(QFont& f, const QDomElement& fontEle);
    // SecenInfo
    void saveSecenInfo(DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle);
    bool loadSecenInfo(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle);
    //

    //生成一个qvariant element
    QDomElement createVariantValueElement(QDomDocument& doc, const QVariant& var);
    QVariant loadVariantValueElement(const QDomElement& item, const QVariant& defaultVal) const;
    //带提示的属性转double
    qreal attributeToDouble(const QDomElement& item, const QString& att);

protected:
    QString _projectName;
    QString _projectPath;
    DAWorkFlowOperateWidget* _operate;
    DAWorkFlowGraphicsScene* _scene;
};
}  // end of namespace DA

//===================================================
// using DA namespace -- 禁止在头文件using！！
//===================================================

using namespace DA;

//===================================================
// DAProjectPrivate
//===================================================
DAProjectPrivate::DAProjectPrivate(DAProject* p, DAWorkFlowOperateWidget* widget)
    : q_ptr(p), _operate(widget), _scene(nullptr)
{
    _scene = _operate->getWorkFlowGraphicsScene();
}

/**
 * @brief 保存工作流相关信息
 * @param workflow
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveWorkflow(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    //保存工作流的扩展信息
    QElapsedTimer tes;
    tes.start();
    QDomElement externEle = doc.createElement("extern");
    workflow->saveExternInfoToXml(&doc, &externEle);
    workflowEle.appendChild(externEle);
    qDebug() << DAProject::tr("save workflow extern info cost: %1 ms").arg(tes.restart());
    //保存所有节点
    saveNodes(workflow, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow nodes cost: %1 ms").arg(tes.restart());
    //保存所有连接
    saveNodeLinks(workflow, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow links cost: %1 ms").arg(tes.restart());
    //保存特殊的item。例如文本
    saveSpecialItem(_scene, doc, workflowEle);
    qDebug() << DAProject::tr("save special item cost: %1 ms").arg(tes.restart());
    //保存工厂相关信息，包括工厂的扩展信息，工厂的信息一般在node和link之后保存
    saveFactoryInfo(workflow, doc, workflowEle);
    qDebug() << DAProject::tr("save workflow factory info cost: %1 ms").arg(tes.restart());
    //保存scene信息
    saveSecenInfo(_scene, doc, workflowEle);
    qDebug() << DAProject::tr("save secen info cost: %1 ms").arg(tes.restart());
}

/**
 * @brief 加载工作流相关信息
 * @param workflow
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadWorkflow(DAWorkFlow* workflow, const QDomElement& workflowEle)
{
    //加载扩展信息
    QElapsedTimer tes;
    tes.start();
    QDomElement externEle = workflowEle.firstChildElement("extern");
    workflow->loadExternInfoFromXml(&externEle);
    qDebug() << DAProject::tr("load workflow extern info cost: %1 ms").arg(tes.restart());

    if (!loadNodes(workflow, workflowEle)) {
        qCritical() << DAProject::tr("load nodes occurce error");
    }
    qDebug() << DAProject::tr("load workflow nodes cost: %1 ms").arg(tes.restart());

    if (!loadNodeLinks(_scene, workflow, workflowEle)) {
        qCritical() << DAProject::tr("load nodes link occurce error");
    }
    qDebug() << DAProject::tr("load workflow links cost: %1 ms").arg(tes.restart());
    //加载工厂
    //! 注意，工厂的加载要在节点和连接之后，工厂一般维护着全局的数据，因此
    //! 额外信息一般是在节点和连接都完成后进行加载
    if (!loadFactoryInfo(workflow, workflowEle)) {
        qCritical() << DAProject::tr("load factorys occurce error");
    }
    qDebug() << DAProject::tr("load workflow factory info cost: %1 ms").arg(tes.restart());

    if (!loadSpecialItem(_scene, workflowEle)) {
        qCritical() << DAProject::tr("load special item occurce error");
    }
    qDebug() << DAProject::tr("load special item cost: %1 ms").arg(tes.restart());

    if (!loadSecenInfo(_scene, workflowEle)) {
        qCritical() << DAProject::tr("load scene info occurce error");
    }
    qDebug() << DAProject::tr("load secen info cost: %1 ms").arg(tes.restart());
    return true;
}

/**
 * @brief 保存工厂相关的扩展信息
 * @param workflow
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveFactoryInfo(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    QList< DAAbstractNodeFactory* > factorys = workflow->factorys();
    //创建节点
    QDomElement factorysEle = doc.createElement("factorys");
    for (const DAAbstractNodeFactory* fac : qAsConst(factorys)) {
        QDomElement factoryEle = doc.createElement("factory");
        factoryEle.setAttribute("name", fac->factoryName());
        QDomElement externEle = doc.createElement("extern");
        fac->saveExternInfoToXml(&doc, &externEle);  //保存扩展信息
        factoryEle.appendChild(externEle);
        factorysEle.appendChild(factoryEle);
    }
    workflowEle.appendChild(factorysEle);
}

/**
 * @brief 加载工厂相关的扩展信息
 * @param workflow
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadFactoryInfo(DAWorkFlow* workflow, const QDomElement& workflowEle)
{
    QDomElement factorysEle     = workflowEle.firstChildElement("factorys");
    QDomNodeList factoryEleList = factorysEle.childNodes();

    for (int i = 0; i < factoryEleList.size(); ++i) {
        QDomElement factoryEle = factoryEleList.at(i).toElement();
        if (factoryEle.tagName() != "factory") {
            qWarning() << DAProject::tr("find unknow tag <%1> under <factorys> element").arg(factoryEle.tagName());
            continue;
        }
        QString factoryname        = factoryEle.attribute("name");
        DAAbstractNodeFactory* fac = workflow->getFactory(factoryname);
        if (fac == nullptr) {
            qCritical() << DAProject::tr("can not find factory named %1").arg(factoryname);
            continue;
        }
        QDomElement externEle = factoryEle.firstChildElement("extern");
        fac->loadExternInfoFromXml(&externEle);  //加载工厂的信息
    }
    return true;
}

void DAProjectPrivate::saveNodes(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    const QList< DAAbstractNode::SharedPointer >& nodes = workflow->nodes();

    QDomElement nodesEle = doc.createElement("nodes");
    for (const DAAbstractNode::SharedPointer& node : qAsConst(nodes)) {
        QDomElement nodeEle = doc.createElement("node");
        nodeEle.setAttribute("id", node->getID());
        nodeEle.setAttribute("name", node->getNodeName());
        nodeEle.setAttribute("protoType", node->getNodePrototype());

        //保存节点input和output的key和propertys
        saveNodeInputOutput(node, doc, nodeEle);
        //保存节点属性
        saveNodePropertys(node, doc, nodeEle);
        //保存额外信息
        node->saveExternInfoToXml(&doc, &nodeEle);
        //添加节点Item信息
        saveNodeItem(node, doc, nodeEle);
        nodesEle.appendChild(nodeEle);
    }
    workflowEle.appendChild(nodesEle);
}

bool DAProjectPrivate::loadNodes(DAWorkFlow* workflow, const QDomElement& workflowEle)
{
    QDomElement nodesEle   = workflowEle.firstChildElement("nodes");
    QDomNodeList nodeslist = nodesEle.childNodes();

    for (int i = 0; i < nodeslist.size(); ++i) {
        QDomElement nodeEle = nodeslist.at(i).toElement();
        if (nodeEle.tagName() != "node") {
            qDebug() << "nodeEle.tagName()=" << nodeEle.tagName() << ",skip and continue";
            continue;
        }
        bool isok     = false;
        qulonglong id = nodeEle.attribute("id").toULongLong(&isok);
        if (!isok) {
            qWarning() << DAProject::tr("node's id=%1 can not conver to qulonglong type ,will skip this node").arg(nodeEle.attribute("id"));
            continue;
        }
        QString name      = nodeEle.attribute("name");
        QString protoType = nodeEle.attribute("protoType");
        //创建节点
        DANodeMetaData metadata            = workflow->getNodeMetaData(protoType);
        DAAbstractNode::SharedPointer node = workflow->createNode(metadata);
        if (nullptr == node) {
            qWarning() << DAProject::tr("workflow can not create note by "
                                        "metadata(prototype=%1,name=%2,group=%3),will skip this node")
                                  .arg(metadata.getNodePrototype(), metadata.getNodeName(), metadata.getGroup());
            continue;
        }

        node->setID(id);
        node->setNodeName(name);
        //加载节点的输入输出
        loadNodeInPutOutputKey(node, nodeEle);
        //加载节点的属性
        loadNodePropertys(node, nodeEle);
        //加载额外信息
        node->loadExternInfoFromXml(&nodeEle);
        //加载item
        loadNodeItem(_scene, node, nodeEle);
        //最后再添加
        workflow->addNode(node);
    }
    return true;
}

/**
 * @brief 生成一个qvariant element
 * @param doc
 * @param v
 * @return
 */
QDomElement DAProjectPrivate::createVariantValueElement(QDomDocument& doc, const QVariant& var)
{
    QDomElement varEle = doc.createElement("variant");
    QString vartype    = var.typeName();
    varEle.setAttribute("type", vartype);
    QDomText t = doc.createTextNode(DA::variantToString(var));
    varEle.appendChild(t);
    return varEle;
}

QVariant DAProjectPrivate::loadVariantValueElement(const QDomElement& item, const QVariant& defaultVal) const
{
    if (item.isNull()) {
        return (defaultVal);
    }
    QString vartype = item.attribute("type");
    QString varstr  = item.text();
    return (DA::stringToVariant(varstr, vartype));
}
/**
 * @brief 带警告的attribute转double
 * @param item
 * @param att
 * @return
 */
qreal DAProjectPrivate::attributeToDouble(const QDomElement& item, const QString& att)
{
    bool isok = false;
    qreal r   = item.attribute(att).toDouble(&isok);
    if (!isok) {
        qWarning() << DAProject::tr("The attribute %1=%2 under the tag %3 cannot be converted to double ")
                              .arg(att, item.attribute(att), item.tagName());
    }
    return r;
}

/**
 * @brief DAProjectPrivate::saveNodeInputOutput
 * @param node
 * @param doc
 * @param nodeEle
 */
void DAProjectPrivate::saveNodeInputOutput(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    QDomElement inputsEle      = doc.createElement("inputs");
    QList< QString > inputKeys = node->getInputKeys();
    for (const auto& key : qAsConst(inputKeys)) {
        //添加input
        QDomElement inputEle = doc.createElement("input");
        QDomElement nameEle  = doc.createElement("name");
        QDomText nameText    = doc.createTextNode(key);
        nameEle.appendChild(nameText);
        inputEle.appendChild(nameEle);
        QVariant v = node->getInputData(key);
        if (v.isValid()) {
            QDomElement varEle = createVariantValueElement(doc, v);
            inputEle.appendChild(varEle);
        }
        inputsEle.appendChild(inputEle);
    }

    QDomElement outputsEle      = doc.createElement("outputs");
    QList< QString > outputKeys = node->getOutputKeys();
    for (const auto& key : qAsConst(outputKeys)) {
        //添加output
        QDomElement outputEle = doc.createElement("output");
        QDomElement nameEle   = doc.createElement("name");
        QDomText nameText     = doc.createTextNode(key);
        nameEle.appendChild(nameText);
        outputEle.appendChild(nameEle);
        QVariant v = node->getOutputData(key);
        if (v.isValid()) {
            QDomElement varEle = createVariantValueElement(doc, v);
            outputEle.appendChild(varEle);
        }
        outputsEle.appendChild(outputEle);
    }
    nodeEle.appendChild(inputsEle);
    nodeEle.appendChild(outputsEle);
}

bool DAProjectPrivate::loadNodeInPutOutputKey(DAAbstractNode::SharedPointer& node, const QDomElement& eleNode)
{
    QDomElement e = eleNode.firstChildElement("inputs");
    if (!e.isNull()) {
        QDomNodeList ks = e.childNodes();
        for (int i = 0; i < ks.size(); ++i) {
            QDomElement inputEle = ks.at(i).toElement();
            if (!inputEle.isNull() && inputEle.tagName() != "input") {
                continue;
            }
            QDomElement nameEle = inputEle.firstChildElement("name");
            if (nameEle.isNull()) {
                qWarning() << DAProject::tr("node(prototype=%1,name=%2,group=%3) %4 tag loss child tag <name>")
                                      .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup(), ks.at(i).nodeName());
                continue;
            }
            QString key = nameEle.text();
            node->addInputKey(key);
            QDomElement variantEle = inputEle.firstChildElement("variant");
            if (!variantEle.isNull()) {
                QVariant d = loadVariantValueElement(variantEle, QVariant());
                node->setInputData(key, d);
            }
        }
    }

    e = eleNode.firstChildElement("outputs");
    if (!e.isNull()) {
        QDomNodeList ks = e.childNodes();
        for (int i = 0; i < ks.size(); ++i) {
            QDomElement outputEle = ks.at(i).toElement();
            if (!outputEle.isNull() && outputEle.tagName() != "output") {
                continue;
            }
            QDomElement nameEle = outputEle.firstChildElement("name");
            if (nameEle.isNull()) {
                qWarning() << DAProject::tr("node(prototype=%1,name=%2,group=%3) %4 tag loss child tag <name>")
                                      .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup(), ks.at(i).nodeName());
                continue;
            }
            QString key = nameEle.text();
            node->addOutputKey(key);
            QDomElement variantEle = outputEle.firstChildElement("variant");
            if (!variantEle.isNull()) {
                QVariant d = loadVariantValueElement(variantEle, QVariant());
                node->setOutputData(key, d);
            }
        }
    }
    return true;
}
/**
 * @brief 保存节点的附加属性
 * @param node
 * @param doc
 * @param nodeEle
 */
void DAProjectPrivate::saveNodePropertys(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    QDomElement propertysEle      = doc.createElement("propertys");
    QList< QString > propertyKeys = node->getPropertyKeys();
    for (const QString& k : qAsConst(propertyKeys)) {
        QVariant v = node->getProperty(k);
        if (!v.isValid()) {
            continue;
        }
        QDomElement nameEle = doc.createElement("name");
        QDomText nameVal    = doc.createTextNode(k);
        nameEle.appendChild(nameVal);
        QDomElement varEle      = createVariantValueElement(doc, v);
        QDomElement propertyEle = doc.createElement("property");
        propertyEle.appendChild(nameEle);
        propertyEle.appendChild(varEle);
        propertysEle.appendChild(propertyEle);
    }
    nodeEle.appendChild(propertysEle);
}

bool DAProjectPrivate::loadNodePropertys(DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle)
{
    QDomElement propertysEle = nodeEle.firstChildElement("propertys");
    if (propertysEle.isNull()) {
        return false;
    }
    QDomNodeList propertyslist = propertysEle.childNodes();
    for (int i = 0; i < propertyslist.size(); ++i) {
        QDomElement propertyEle = propertyslist.at(i).toElement();
        if (propertyEle.tagName() != "property") {
            continue;
        }
        QDomElement nameEle = propertyEle.firstChildElement("name");
        if (nameEle.isNull()) {
            continue;
        }
        QDomElement variantEle = propertyEle.firstChildElement("variant");
        if (variantEle.isNull()) {
            continue;
        }
        QVariant v = loadVariantValueElement(variantEle, QVariant());
        node->setProperty(nameEle.text(), v);
    }
    return true;
}

void DAProjectPrivate::saveNodeItem(const DAAbstractNode::SharedPointer& node, QDomDocument& doc, QDomElement& nodeEle)
{
    DAAbstractNodeGraphicsItem* item = node->graphicsItem();
    QDomElement itemEle              = doc.createElement("item");

    //添加item属性
    QPointF pos = item->pos();
    itemEle.setAttribute("x", pos.x());
    itemEle.setAttribute("y", pos.y());
    itemEle.setAttribute("z", item->zValue());
    itemEle.setAttribute("rotation", item->rotation());
    itemEle.setAttribute("scale", item->scale());
    itemEle.setAttribute("enableResize", item->isEnableResize());

    //添加bodySize属性
    QDomElement bodySizeEle = doc.createElement("bodySize");
    bodySizeEle.setAttribute("width", item->getBodySize().width());
    bodySizeEle.setAttribute("height", item->getBodySize().height());
    bodySizeEle.setAttribute("minWidth", item->getBodyMinimumSize().width());
    bodySizeEle.setAttribute("minHeight", item->getBodyMinimumSize().height());
    bodySizeEle.setAttribute("maxWidth", item->getBodyMaximumSize().width());
    bodySizeEle.setAttribute("maxHeight", item->getBodyMaximumSize().height());

    //添加controlerSize属性
    QDomElement controlerEle = doc.createElement("controlerSize");
    controlerEle.setAttribute("width", item->getControlerSize().width());
    controlerEle.setAttribute("height", item->getControlerSize().height());

    //添加linkpoint的附加属性
    QDomElement linkpointEle     = doc.createElement("linkpoints");
    QList< DANodeLinkPoint > lps = item->getLinkPoints();
    for (const DANodeLinkPoint& p : qAsConst(lps)) {
        QDomElement pEle = doc.createElement("linkpoint");
        pEle.setAttribute("name", p.name);
        pEle.setAttribute("direction", DA::enumToString(p.direction));
        linkpointEle.appendChild(pEle);
    }

    //组织xml
    itemEle.appendChild(bodySizeEle);
    itemEle.appendChild(controlerEle);
    itemEle.appendChild(linkpointEle);
    nodeEle.appendChild(itemEle);
}

bool DAProjectPrivate::loadNodeItem(DAWorkFlowGraphicsScene* scene, DAAbstractNode::SharedPointer& node, const QDomElement& nodeEle)
{
    QDomElement ele = nodeEle.firstChildElement("item");
    if (ele.isNull()) {
        return false;
    }
    DAAbstractNodeGraphicsItem* item = node->createGraphicsItem();
    if (nullptr == item) {
        qWarning() << DAProject::tr("node metadata(prototype=%1,name=%2,group=%3) can not create graphics item")
                              .arg(node->getNodePrototype(), node->getNodeName(), node->getNodeGroup());
        return false;
    }
    auto to_double_with_warning = [](const QDomElement& e, const QString& attName, const DAAbstractNode::SharedPointer& n) -> qreal {
        bool ok  = false;
        qreal dv = e.attribute(attName).toDouble(&ok);
        if (!ok) {
            qWarning() << DAProject::tr("node item metadata(prototype=%1,name=%2,group=%3) have abnormal data under %4 "
                                        "tag,attribute %5=%6 can not convert to double")
                                  .arg(n->getNodePrototype(), n->getNodeName(), n->getNodeGroup(), e.nodeName(), attName, e.attribute(attName));
        }
        return dv;
    };
    item->setPos(to_double_with_warning(ele, "x", node), to_double_with_warning(ele, "y", node));
    item->setZValue(to_double_with_warning(ele, "z", node));
    item->setRotation(to_double_with_warning(ele, "rotation", node));
    item->setScale(to_double_with_warning(ele, "scale", node));
    item->setEnableResize(ele.attribute("enableResize").toInt());

    QDomNode n = ele.firstChild();
    while (!n.isNull()) {
        QDomElement e = n.toElement();
        if (e.tagName() == "bodySize") {
            item->setBodySize(QSizeF(to_double_with_warning(e, "width", node), to_double_with_warning(e, "height", node)));
            item->setBodyMinimumSize(
                    QSizeF(to_double_with_warning(e, "minWidth", node), to_double_with_warning(e, "minHeight", node)));
            item->setBodyMaximumSize(
                    QSizeF(to_double_with_warning(e, "maxWidth", node), to_double_with_warning(e, "maxHeight", node)));
        } else if (e.tagName() == "controlerSize") {
            item->setControlerSize(QSizeF(to_double_with_warning(e, "width", node), to_double_with_warning(e, "height", node)));
        } else if (e.tagName() == "linkpoints") {
            QDomNodeList lpnodes = e.elementsByTagName("linkpoint");
            for (int i = 0; i < lpnodes.size(); ++i) {
                QDomElement lpEle = lpnodes.at(i).toElement();
                QString name      = lpEle.attribute("name");
                if (name.isEmpty()) {
                    continue;
                }
                item->setNodeLinkPointDirection(name, DA::stringToEnum(lpEle.attribute("direction"), DANodeLinkPoint::East));
                qDebug() << "name=" << name
                         << ",direction=" << DA::stringToEnum(lpEle.attribute("direction"), DANodeLinkPoint::East);
            }
        }
        n = n.nextSibling();
    }
    scene->addItem(item);
    return true;
}

void DAProjectPrivate::saveNodeLinks(const DAWorkFlow* workflow, QDomDocument& doc, QDomElement& workflowEle)
{
    const QList< DAAbstractNode::SharedPointer >& nodes = workflow->nodes();
    //
    QDomElement nodeLinkEle = doc.createElement("links");

    QSet< DAAbstractNodeLinkGraphicsItem* > itemSet;
    for (auto node : nodes) {
        auto links = node->graphicsItem()->getLinkItems();
        for (auto link : qAsConst(links)) {
            itemSet.insert(link);
        }
    }

    for (auto link : itemSet) {
        QDomElement linkEle = doc.createElement("link");
        QDomElement fromEle = doc.createElement("from");
        fromEle.setAttribute("id", link->fromNode()->getID());
        fromEle.setAttribute("name", link->fromNodeLinkPoint().name);
        QDomElement toEle = doc.createElement("to");
        toEle.setAttribute("id", link->toNode()->getID());
        toEle.setAttribute("name", link->toNodeLinkPoint().name);

        QDomElement pointEle = doc.createElement("linkPoint");
        pointEle.setAttribute("visible", link->isLinkPointNameVisible());
        pointEle.setAttribute("fromTextColor",
                              link->getLinkPointNameTextColor(DAAbstractNodeLinkGraphicsItem::OrientationFrom).name());
        pointEle.setAttribute("toTextColor",
                              link->getLinkPointNameTextColor(DAAbstractNodeLinkGraphicsItem::OrientationTo).name());
        pointEle.setAttribute("fromPositionOffset",
                              link->getLinkPointNamePositionOffset(DAAbstractNodeLinkGraphicsItem::OrientationFrom));
        pointEle.setAttribute("toPositionOffset", link->getLinkPointNamePositionOffset(DAAbstractNodeLinkGraphicsItem::OrientationTo));
        QDomElement endPointEle = doc.createElement("endPoint");
        endPointEle.setAttribute("toType", DA::enumToString(link->getEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationTo)));
        endPointEle.setAttribute("fromType",
                                 DA::enumToString(link->getEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationFrom)));
        endPointEle.setAttribute("size", link->getEndPointSize());

        QDomElement lineEle = doc.createElement("linkLine");
        lineEle.setAttribute("style", DA::enumToString(link->getLinkLineStyle()));

        QDomElement linePenEle = doc.createElement("linePen");
        linePenEle.setAttribute("color", link->getLinePen().color().name());
        linePenEle.setAttribute("style", (link->getLinePen().style()));
        linePenEle.setAttribute("width", link->getLinePen().width());

        linkEle.appendChild(fromEle);
        linkEle.appendChild(toEle);
        linkEle.appendChild(pointEle);
        linkEle.appendChild(endPointEle);
        linkEle.appendChild(lineEle);
        linkEle.appendChild(linePenEle);

        nodeLinkEle.appendChild(linkEle);
    }

    workflowEle.appendChild(nodeLinkEle);
}

bool DAProjectPrivate::loadNodeLinks(DAWorkFlowGraphicsScene* scene, DAWorkFlow* wf, const QDomElement& workflowEle)
{
    QDomElement linksEle = workflowEle.firstChildElement("links");
    QDomNodeList list    = linksEle.childNodes();
    for (int i = 0; i < list.size(); ++i) {
        QDomElement linkEle = list.at(i).toElement();
        if (linkEle.tagName() != "link") {
            continue;
        }
        QDomElement fromEle = linkEle.firstChildElement("from");
        QDomElement toEle   = linkEle.firstChildElement("to");
        if (fromEle.isNull() || toEle.isNull()) {
            continue;
        }
        bool ok = false;

        qulonglong id                          = fromEle.attribute("id").toULongLong(&ok);
        QString fromKey                        = fromEle.attribute("name");
        DAAbstractNode::SharedPointer fromNode = wf->getNode(id);
        if (!ok || nullptr == fromNode) {
            qWarning() << DAProject::tr("link info can not find node in workflow,id = %1").arg(fromEle.attribute("id"));
            continue;
        }
        id                                   = toEle.attribute("id").toULongLong(&ok);
        QString toKey                        = toEle.attribute("name");
        DAAbstractNode::SharedPointer toNode = wf->getNode(id);
        if (!ok || nullptr == toNode) {
            qWarning() << DAProject::tr("link info can not find node in workflow,id = %1").arg(toEle.attribute("id"));
            continue;
        }
        DAAbstractNodeGraphicsItem* fromItem = fromNode->graphicsItem();
        DAAbstractNodeGraphicsItem* toItem   = toNode->graphicsItem();
        if (nullptr == fromItem || nullptr == toItem) {
            qWarning() << DAProject::tr("can not get item by node");
            continue;
        }
        QScopedPointer< DAStandardNodeLinkGraphicsItem > linkitem(new DAStandardNodeLinkGraphicsItem());
        if (!linkitem->attachFrom(fromItem, fromKey)) {
            qWarning() << DAProject::tr("link item can not attach from node item(%1) with key=%2").arg(fromItem->getNodeName(), fromKey);
            continue;
        }
        if (!linkitem->attachTo(toItem, toKey)) {
            qWarning() << DAProject::tr("link item can not attach to node item(%1) with key=%2").arg(toItem->getNodeName(), toKey);
            continue;
        }
        QDomElement pointEle    = linkEle.firstChildElement("linkPoint");
        QDomElement endPointEle = linkEle.firstChildElement("endPoint");
        QDomElement lineEle     = linkEle.firstChildElement("linkLine");
        QDomElement linePenEle  = linkEle.firstChildElement("linePen");
        if (!pointEle.isNull()) {
            bool visible = pointEle.attribute("visible").toInt();
            QColor fromTextColor(pointEle.attribute("fromTextColor"));
            QColor toTextColor(pointEle.attribute("toTextColor"));
            int fromPositionOffset = pointEle.attribute("fromPositionOffset").toInt();
            int toPositionOffset   = pointEle.attribute("toPositionOffset").toInt();
            linkitem->setLinkPointNameVisible(visible);
            linkitem->setLinkPointNamePositionOffset(fromPositionOffset, DAAbstractNodeLinkGraphicsItem::OrientationFrom);
            linkitem->setLinkPointNamePositionOffset(toPositionOffset, DAAbstractNodeLinkGraphicsItem::OrientationTo);
            linkitem->setLinkPointNameTextColor(fromTextColor, DAAbstractNodeLinkGraphicsItem::OrientationFrom);
            linkitem->setLinkPointNameTextColor(toTextColor, DAAbstractNodeLinkGraphicsItem::OrientationTo);
        }
        if (!lineEle.isNull()) {
            DAAbstractNodeLinkGraphicsItem::LinkLineStyle s = DA::stringToEnum(lineEle.attribute("style"),
                                                                               DAAbstractNodeLinkGraphicsItem::LinkLineKnuckle);
            linkitem->setLinkLineStyle(s);
        }
        if (!endPointEle.isNull()) {
            DAAbstractNodeLinkGraphicsItem::EndPointType etTo   = DA::stringToEnum(endPointEle.attribute("toType"),
                                                                                 DAAbstractNodeLinkGraphicsItem::EndPointNone);
            DAAbstractNodeLinkGraphicsItem::EndPointType etFrom = DA::stringToEnum(endPointEle.attribute("fromType"),
                                                                                   DAAbstractNodeLinkGraphicsItem::EndPointNone);
            int size                                            = endPointEle.attribute("size").toInt();
            linkitem->setEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationTo, etTo);
            linkitem->setEndPointType(DAAbstractNodeLinkGraphicsItem::OrientationFrom, etFrom);
            if (size > 0) {
                linkitem->setEndPointSize(size);
            }
        }
        if (!linePenEle.isNull()) {
            QColor pencolor(linePenEle.attribute("color"));
            int width          = linePenEle.attribute("width").toInt();
            Qt::PenStyle style = static_cast< Qt::PenStyle >(linePenEle.attribute("style").toInt());
            QPen pen(pencolor);
            pen.setWidth(width);
            pen.setStyle(style);
            linkitem->setLinePen(pen);
        }
        DAStandardNodeLinkGraphicsItem* rawlinkitem = linkitem.take();
        scene->addItem(rawlinkitem);
        rawlinkitem->updatePos();
    }
    return true;
}

/**
 * @brief 保存特殊的item
 * @param scene
 * @param doc
 * @param workflowEle
 */
void DAProjectPrivate::saveSpecialItem(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle)
{
    QDomElement specialItemsElement = doc.createElement("special-items");
    //保存文本
    QDomElement textItemsElement = doc.createElement("text-items");
    saveTextItems(scene, doc, textItemsElement);
    specialItemsElement.appendChild(textItemsElement);

    workflowEle.appendChild(specialItemsElement);
}

/**
 * @brief 加载特殊的item
 * @param scene
 * @param workflowEle
 * @return
 */
bool DAProjectPrivate::loadSpecialItem(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle)
{
    QDomElement specialItemsElement = workflowEle.firstChildElement("special-items");

    QDomNodeList childNodes = specialItemsElement.childNodes();
    for (int i = 0; i < childNodes.size(); ++i) {
        QDomElement specialItemGroup = childNodes.at(i).toElement();
        if (specialItemGroup.tagName() == "text-items") {
            loadTextItems(scene, specialItemGroup);
        }
        // else if(specialItemGroup.tagName() == "其他类别")
    }
    return true;
}

/**
 * @brief 保存scene的文本item
 * @param scene
 * @param doc
 * @param textItemsEle
 */
void DAProjectPrivate::saveTextItems(const DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& textItemsEle)
{
    QList< DAStandardGraphicsTextItem* > textItems = scene->getTextGraphicsItems();
    for (const DAStandardGraphicsTextItem* textitem : qAsConst(textItems)) {
        QDomElement textItemEle = doc.createElement("text-item");
        saveTextItem(textitem, doc, textItemEle);
        textItemsEle.appendChild(textItemEle);
    }
}

/**
 * @brief DAProjectPrivate::loadTextItems
 * @param scene
 * @param textItemsEle
 * @return
 */
bool DAProjectPrivate::loadTextItems(DAWorkFlowGraphicsScene* scene, const QDomElement& textItemsEle)
{
    QDomNodeList childNodes = textItemsEle.childNodes();
    for (int i = 0; i < childNodes.size(); ++i) {
        QDomElement textitemEle = childNodes.at(i).toElement();
        if (textitemEle.tagName() == "text-item") {
            QScopedPointer< DAStandardGraphicsTextItem > textitem(new DAStandardGraphicsTextItem());
            if (loadTextItem(textitem.data(), textitemEle)) {
                scene->addItem(textitem.take());
            }
        }
        // else if(specialItemGroup.tagName() == "其他类别")
    }
    return true;
}

/**
 * @brief 保存文本
 * @param item
 * @param doc
 * @param textItemsEle
 */
void DAProjectPrivate::saveTextItem(const DAStandardGraphicsTextItem* item, QDomDocument& doc, QDomElement& textItemEle)
{
    textItemEle.setAttribute("x", item->x());
    textItemEle.setAttribute("y", item->y());
    textItemEle.setAttribute("color", item->defaultTextColor().name());
    QDomElement fontEle = doc.createElement("font");
    saveFont(item->font(), doc, fontEle);
    textItemEle.appendChild(fontEle);
    QDomElement textEle = doc.createElement("text");
    QDomText text       = doc.createTextNode(item->toPlainText());
    textEle.appendChild(text);
    textItemEle.appendChild(textEle);
}

/**
 * @brief 加载文本
 * @param item
 * @param textItemsEle
 * @return
 */
bool DAProjectPrivate::loadTextItem(DAStandardGraphicsTextItem* item, const QDomElement& textItemEle)
{
    QPointF pos;
    pos.setX(attributeToDouble(textItemEle, "x"));
    pos.setY(attributeToDouble(textItemEle, "y"));
    QColor color(textItemEle.attribute("color"));
    QFont f;
    QDomElement fontEle = textItemEle.firstChildElement("font");
    loadFont(f, fontEle);
    QDomElement textEle = textItemEle.firstChildElement("text");
    item->setPlainText(textEle.text());
    item->setDefaultTextColor(color);
    item->setFont(f);
    item->setPos(pos);
    return true;
}

/**
 * @brief 保存字体
 * @param f
 * @param doc
 * @param fontEle
 */
void DAProjectPrivate::saveFont(const QFont& f, QDomDocument& doc, QDomElement& fontEle)
{
    Q_UNUSED(doc);
    fontEle.setAttribute("bold", f.bold());
    fontEle.setAttribute("italic", f.italic());
    fontEle.setAttribute("pointSizeF", f.pointSizeF());
    fontEle.setAttribute("weight", f.weight());
    fontEle.setAttribute("family", f.family());
}

/**
 * @brief 加载字体
 * @param f
 * @param fontEle
 * @return
 */
bool DAProjectPrivate::loadFont(QFont& f, const QDomElement& fontEle)
{
    f.setBold(fontEle.attribute("bold").toInt());
    f.setItalic(fontEle.attribute("italic").toInt());
    f.setPointSizeF(fontEle.attribute("pointSizeF").toDouble());
    f.setWeight(fontEle.attribute("weight").toInt());
    f.setFamily(fontEle.attribute("family"));
    return true;
}

void DAProjectPrivate::saveSecenInfo(DAWorkFlowGraphicsScene* scene, QDomDocument& doc, QDomElement& workflowEle)
{
    QDomElement sceneEle = doc.createElement("scene");
    QRectF sceneRect     = scene->sceneRect();
    sceneEle.setAttribute("x", sceneRect.x());
    sceneEle.setAttribute("y", sceneRect.y());
    sceneEle.setAttribute("width", sceneRect.width());
    sceneEle.setAttribute("height", sceneRect.height());
    auto item = scene->getBackgroundPixmapItem();
    if (nullptr != item) {
        QPixmap pixmap = item->getPixmap();
        if (!pixmap.isNull()) {
            QBuffer buff;
            pixmap.save(&buff, "PNG");
            QByteArray dataimg;
            //图像转换为数据
            dataimg.append(buff.data());
            //图片保存在字符串中
            QString imgStr = dataimg.toBase64();
            //以Base64保存到工程文件
            QDomElement imageEle = doc.createElement("background");
            QDomText imageText   = doc.createTextNode(imgStr);
            imageEle.appendChild(imageText);
            sceneEle.appendChild(imageEle);
        }
    }

    workflowEle.appendChild(sceneEle);
}

bool DAProjectPrivate::loadSecenInfo(DAWorkFlowGraphicsScene* scene, const QDomElement& workflowEle)
{
    QDomElement sceneEle = workflowEle.firstChildElement("scene");
    if (sceneEle.isNull()) {
        return false;
    }
    qreal x      = attributeToDouble(sceneEle, "x");
    qreal y      = attributeToDouble(sceneEle, "y");
    qreal width  = attributeToDouble(sceneEle, "width");
    qreal height = attributeToDouble(sceneEle, "height");
    scene->setSceneRect(x, y, width, height);

    QDomElement imageEle = sceneEle.firstChildElement("background");
    if (!imageEle.isNull()) {
        QString imgStr = imageEle.text();

        //字符串转图像数据
        QByteArray imgData = QByteArray::fromBase64(imgStr.toUtf8());
        QPixmap pixmap;
        //从数据载入图像
        pixmap.loadFromData(imgData);
        if (!pixmap.isNull()) {
            scene->setBackgroundPixmap(pixmap);
        }
    }
    return true;
}

////////////////////////////////////////////////////

DAProject::DAProject(DAWorkFlowOperateWidget* widget, QObject* p)
    : QObject(p), d_ptr(new DAProjectPrivate(this, widget))
{
}

DAProject::~DAProject()
{
}

/**
 * @brief 获取工程名
 * @return
 */
QString DAProject::getProjectName() const
{
    return (d_ptr->_projectName);
}

/**
 * @brief 设置工程名
 * @param projectName
 */
void DAProject::setProjectName(const QString& projectName)
{
    d_ptr->_projectName = projectName;
}

QString DAProject::getProjectPath() const
{
    return d_ptr->_projectPath;
}

void DAProject::setProjectPath(const QString& projectPath)
{
    d_ptr->_projectPath = projectPath;
}

DAWorkFlowGraphicsScene* DAProject::getScene() const
{
    return d_ptr->_scene;
}

void DAProject::setScene(DAWorkFlowGraphicsScene* scene)
{
    d_ptr->_scene = scene;
}

bool DAProject::save(const QString& path)
{
    QFile file(path);
    if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
        qCritical() << tr("open failed,path is %1").arg(path);
        return false;
    }
    QDomDocument doc;
    QDomProcessingInstruction processInstruction = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
    doc.appendChild(processInstruction);
    QDomElement root = doc.createElement("root");
    root.setAttribute("type", "project");
    doc.appendChild(root);
    QDomElement project = doc.createElement("project");
    root.appendChild(project);
    QDomElement workflowEle = doc.createElement("workflow");
    d_ptr->saveWorkflow(d_ptr->_scene->getWorkflow(), doc, workflowEle);
    project.appendChild(workflowEle);
    QTextStream outFile(&file);
    doc.save(outFile, 4);
    file.close();

    return true;
}
/**
 * @brief 加载
 * @param path
 * @return
 */
bool DAProject::load(const QString& path)
{
    //加载之前先清空
    clear();
    QDomDocument doc;
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly)) {
        return false;
    }
    QString error;
    if (!doc.setContent(&file, &error)) {
        qCritical() << "load setContent error:" << error;
        file.close();
        return false;
    }
    file.close();
    bool isok               = true;
    QDomElement docElem     = doc.documentElement();                 // root
    QDomElement proEle      = docElem.firstChildElement("project");  // project
    QDomElement workflowEle = proEle.firstChildElement("workflow");  // workflow
    if (!workflowEle.isNull()) {
        isok &= d_ptr->loadWorkflow(d_ptr->_scene->getWorkflow(), workflowEle);
    }
    return isok;
}

/**
 * @brief 清除工程
 */
void DAProject::clear()
{
    QList< QGraphicsItem* > items = d_ptr->_scene->items();
    for (QGraphicsItem* item : items) {
        d_ptr->_scene->removeItem(item);
    }
    d_ptr->_scene->clear();
    d_ptr->_scene->getWorkflow()->clear();
    d_ptr->_scene->undoStack().clear();
}

QString DAProject::directionToStr(DANodeLinkPoint::Direction direction)
{
    QString str = "";
    switch (direction) {
    case DANodeLinkPoint::East:
        str = "East";
        break;
    case DANodeLinkPoint::South:
        str = "South";
        break;
    case DANodeLinkPoint::West:
        str = "West";
        break;
    case DANodeLinkPoint::North:
        str = "North";
        break;
    default:
        break;
    }
    return (str);
}

DANodeLinkPoint::Direction DAProject::strToDirection(const QString& direction)
{
    DANodeLinkPoint::Direction styleEnum(DANodeLinkPoint::East);
    if (direction == "East") {
        styleEnum = DANodeLinkPoint::East;
    } else if (direction == "South") {
        styleEnum = DANodeLinkPoint::South;
    } else if (direction == "West") {
        styleEnum = DANodeLinkPoint::West;
    } else {
        styleEnum = DANodeLinkPoint::North;
    }
    return (styleEnum);
}
